﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Phoenix.Xna.Framework.Input.Keyboards;
using Phoenix.Xna.Framework.Input.Mice;
using MouseButtons = Phoenix.Xna.Framework.Input.Mice.Buttons;

namespace Phoenix.Xna.Framework.Input
{
    /// <summary>
    /// Manages all connected input devices.
    /// </summary>
    public sealed class InputManager
    {
        /// <summary>
        /// Raised when the state of the keyboard changes.
        /// </summary>
        public event EventHandler KeyboardStateChanged;

        /// <summary>
        /// Raised when the state of the mouse changes.
        /// </summary>
        public event EventHandler MouseStateChanged;

        private class NestedSingleton
        {
            internal static readonly InputManager Singleton = new InputManager();

            static NestedSingleton() { }
        }

        /// <summary>
        /// Gets a singleton instance of the InputService.
        /// </summary>
        public static InputManager Instance
        {
            get { return NestedSingleton.Singleton; }
        }

        private InputManager()
        {
            KeyboardManager.Instance.StateChanged += new EventHandler(OnKeyboardStateChanged);
            MouseManager.Instance.StateChanged += new EventHandler(OnMouseStateChanged);
        }

        /// <summary>
        /// Register the specified input map with the correct device manager.
        /// </summary>
        /// <param name="map">The IInputMap to register.</param>
        internal void RegisterInputMap(IInputMap map)
        {
            if (map is KeyboardKeyMap)
            {
                KeyboardManager.Instance.RegisterMap(map);
            }
            else if (map is MouseAxisMap || map is MouseButtonMap || map is MouseScrollWheelMap)
            {
                MouseManager.Instance.RegisterMap(map);
            }
        }

        private void OnKeyboardStateChanged(object sender, EventArgs e)
        {
            EventHandler handler = KeyboardStateChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        private void OnMouseStateChanged(object sender, EventArgs e)
        {
            EventHandler handler = MouseStateChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        #region IInputService Members

        /// <summary>
        /// Updates all input devices connected to the system.
        /// </summary>
        /// <param name="gameTime">A snapshot of the elapsed game time.</param>
        public void Update(GameTime gameTime)
        {
            KeyboardManager.Instance.Update(gameTime);
            MouseManager.Instance.Update(gameTime);
        }

        /// <summary>
        /// Map the specified keys on the keyboard.
        /// </summary>
        /// <param name="keys">The collection of keys to map.</param>
        /// <returns>The KeyMap object that tracks events for the specified keys.</returns>
        public KeyboardKeyMap MapKeys(List<Keys> keys)
        {
            return MapKeys(keys, KeyboardKeyMap.DefaultHoldThreshold, KeyboardKeyMap.DefaultRepeatRate);
        }

        /// <summary>
        /// Map the specified keys on the keyboard.
        /// </summary>
        /// <param name="keys">The collection of keys to map.</param>
        /// <param name="holdThreshold">The timespan to wait before the map is considered held.</param>
        /// <param name="repeatRate">The timespan between repeating button pressed events.</param>
        /// <returns>The KeyMap object that tracks events for the specified keys.</returns>
        public KeyboardKeyMap MapKeys(List<Keys> keys, int holdThreshold, int repeatRate)
        {
            KeyboardKeyMap map = KeyboardManager.Instance.FindMap(keys);

            if (map != null)
            {
                return map;
            }

            return new KeyboardKeyMap(keys, holdThreshold, repeatRate);
        }

        /// <summary>
        /// Map the mouse axis.
        /// </summary>
        /// <returns>The AxisMap object that tracks mouse movement.</returns>
        public MouseAxisMap MapMouseAxis()
        {
            if (MouseManager.Instance.ContainsMapType<MouseAxisMap>())
            {
                return MouseManager.Instance.FirstOf<MouseAxisMap>();
            }

            return new MouseAxisMap();
        }

        /// <summary>
        /// Map the specified buttons on the mouse.
        /// </summary>
        /// <param name="buttons">The collection of buttons to map.</param>
        /// <returns>The ButtonMap object that tracks events for the specified buttons.</returns>
        public MouseButtonMap MapMouseButtons(List<MouseButtons> buttons)
        {
            return MapMouseButtons(buttons, MouseButtonMap.DefaultHoldThreshold, MouseButtonMap.DefaultRepeatRate);
        }

        /// <summary>
        /// Map the specified buttons on the mouse.
        /// </summary>
        /// <param name="buttons">The collection of buttons to map.</param>
        /// <param name="holdThreshold">The timespan to wait before the map is considered held.</param>
        /// <param name="repeatRate">The timespan between repeating button pressed events.</param>
        /// <returns>The ButtonMap object that tracks events for the specified buttons.</returns>
        public MouseButtonMap MapMouseButtons(List<MouseButtons> buttons, int holdThreshold, int repeatRate)
        {
            MouseButtonMap map = MouseManager.Instance.FindMap(buttons);

            if (map != null)
            {
                return map;
            }

            return new MouseButtonMap(buttons, holdThreshold, repeatRate);
        }

        /// <summary>
        /// Map the scrollwheel on the mouse.
        /// </summary>
        /// <returns>The ScrollWheelMap object that tracks mouse scrollwheel changes.</returns>
        public MouseScrollWheelMap MapMouseScrollWheel()
        {
            if (MouseManager.Instance.ContainsMapType<MouseScrollWheelMap>())
            {
                return MouseManager.Instance.FirstOf<MouseScrollWheelMap>();
            }

            return new MouseScrollWheelMap();
        }

        #endregion
    }
}
